﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using swWindow = System.Windows.Window;

namespace MimicAPI.Window
{
    public static class WindowExtensions
    {
        public static string GetWindowName(this IntPtr hWnd)
        {
            StringBuilder sb = new StringBuilder(hWnd.GetWindowTextLength() + 1);
            hWnd.GetWindowText(sb, sb.Capacity);
            return sb.ToString();
        }

        static string GetWindowPath(this IntPtr hWnd)
        {
            uint dwProcessId;
            hWnd.GetWindowThreadProcessId(out dwProcessId);
            StringBuilder path = new StringBuilder(1024);
            try
            {
                // Note : Some errors can occured here, for instance if the user as insufficient privileges to access the specified process.
                using (Process process = Process.GetProcessById((int)dwProcessId))
                    process.Handle.GetModuleFileNameEx(IntPtr.Zero, path, path.Capacity);
            }
            catch { return ""; }
            return path.ToString();
        }

        static Icon GetWindowIcon(this IntPtr hWnd)
        {
            string path = hWnd.GetWindowPath();
            return !File.Exists(path) ? null : Icon.ExtractAssociatedIcon(path);
        }

        public static BitmapFrame GetWindowBitmapFrame(this IntPtr hWnd)
        {
            // HACK : I failed to find an easier way to get the Icon...
            // http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=935896&SiteID=1
            // (If you have a better way to do that... Please send me a note at mimic@zorglonde.com !)
            using (Icon icon = hWnd.GetWindowIcon())
                if (icon != null)
                    using (MemoryStream stream = new MemoryStream())
                    {
                        icon.Save(stream);
                        return BitmapFrame.Create(stream);
                    }
            return null;
        }

        static StateWindow GetCurrentState(this IntPtr hWnd)
        {
            WindowStyles ws = hWnd.GetWindowLong(GetWindowLongValue.GWL_STYLE);
            if (ws.Contains(WindowStyles.WS_MINIMIZE))
                return StateWindow.SW_MINIMIZE;
            if (ws.Contains(WindowStyles.WS_MAXIMIZE))
                return StateWindow.SW_MAXIMIZE;
            return StateWindow.SW_SHOW;
        }

        /// <summary>Get a list of visible windows</summary>
        /// <param name="wnd"></param>
        /// <param name="includeItself">Should the current window be in the result ?</param>
        /// <param name="includeAllWindows">Should the result contain all visible windows ? (if false, some rules will filter them)</param>
        public static IEnumerable<IntPtr> GetVisibleWindows(this swWindow wnd, bool includeItself, bool includeAllWindows)
        {
            // TODO : Compare this method with the one used by the project "Tray Minimizer" (on CodeProject) to get another way to do that stuff

            IntPtr hFstWnd = new WindowInteropHelper(wnd).Handle;
            if (hFstWnd == IntPtr.Zero)
                yield break;

            // Construct a function to test if a window is visible or not
            Predicate<IntPtr> isVisible = delegate(IntPtr hWnd)
            {
                if ((hWnd == hFstWnd) && !includeItself)
                    return false;
                if (includeAllWindows)
                    return hWnd.GetWindowLong(GetWindowLongValue.GWL_STYLE).Contains(WindowStyles.WS_VISIBLE);
                return hWnd.GetWindowLong(GetWindowLongValue.GWL_STYLE).Contains(WindowStyles.WS_VISIBLE | WindowStyles.WS_GROUP)
                       && (hWnd.GetWindowTextLength() != 0);
            };

            for (IntPtr hWnd = hFstWnd.GetWindow(GetWndCmd.First); hWnd != IntPtr.Zero; hWnd = hWnd.GetWindow(GetWndCmd.Next))
                if (isVisible(hWnd))
                    yield return hWnd;
        }

        static Dictionary<IntPtr, Action> dicoMinizer = new Dictionary<IntPtr, Action>();
        public static void MinUnminInTray(this IntPtr hWnd, Icon defaultIcon)
        {
            hWnd.MinUnminInTray(null, defaultIcon);
        }

        public static void MinUnminInTray(this IntPtr hWnd, IContainer container, Icon defaultIcon)
        {
            if (dicoMinizer.ContainsKey(hWnd))
            {
                dicoMinizer[hWnd]();
                dicoMinizer.Remove(hWnd);
                return;
            }

            StateWindow initialState = hWnd.GetCurrentState();
            NotifyIcon tray = container == null ? new NotifyIcon() : new NotifyIcon(container);
            Icon trayIcon = hWnd.GetWindowIcon() ?? defaultIcon;
            if (trayIcon != null)
                tray.Icon = trayIcon;
            tray.Visible = true;
            // We limit the size to 63 characters (because it is the maximum that is managed by the NotifyIcon element)
            string windowName = hWnd.GetWindowName() ?? "";
            tray.Text = windowName.Length > 63 ? windowName.Substring(0, 63) : windowName;
            Action unMinizeFunction = delegate()
            {
                if (hWnd.IsWindow())
                    hWnd.ShowWindow(initialState);
                else
                    TaskDialog.Show("Failed to find the windows", string.Format("Failed to show '{0}'", tray.Text),
                                    "Well, sorry for the inconvenience. You can report this error to mimic@zorglonde.com !",
                                    TaskDialogButtons.Ok, TaskDialogIcon.Error);
                tray.Visible = false;
                tray.Dispose();
                dicoMinizer.Remove(hWnd);
            };
            dicoMinizer.Add(hWnd, unMinizeFunction);
            tray.Click += (sender, args) => unMinizeFunction();
            hWnd.ShowWindow(StateWindow.SW_HIDE);
        }

        public static void UnMinimizeAll()
        {
            // Note : Here we cannot directly write a foreach on "dicoMinize.Values", because each action modify the dictionary.
            foreach (Action unMinimizeWindows in new List<Action>(dicoMinizer.Values))
                unMinimizeWindows();
        }

        public static void EnableMoving(this swWindow wnd)
        {
            // True iif the last windows messages was either WM_NCRBUTTONDOWN or either WM_NCHITTEST with (Mouse.RightButton == MouseButtonState.Pressed)
            bool RButtonIsPressed = false;

            // Add a hook to handle Win32 messages
            HwndSource.FromHwnd(new WindowInteropHelper(wnd).Handle).AddHook(
                    delegate(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
                    {
                        // Fetch out X & Y out of the message
                        // Cf. http://msdn2.microsoft.com/en-us/library/ms645618.aspx (Description of the parameter lParam)
                        // The low-order word specifies the x-coordinate of the cursor (relative to the upper-left corner of the screen).
                        // The high-order word specifies the y-coordinate of the cursor (relative to the upper-left corner of the screen).
                        int x, y;
                        try
                        {
                            x = lParam.ToInt32() & 0xFFFF;
                            y = (lParam.ToInt32() >> 16) & 0xFFFF;
                        }
                        catch
                        {
                            return IntPtr.Zero;
                        }

                        // We don't do anything if we are near the edges of our element
                        if ((x <= wnd.Left + 5) || (x >= wnd.Left + wnd.Width - 5) || (y <= wnd.Top + 5) || (y >= wnd.Top + wnd.Height - 5))
                            return IntPtr.Zero;

                        switch ((WindowMessages)msg)
                        {
                            case WindowMessages.WM_NCHITTEST:
                                handled = !RButtonIsPressed;
                                RButtonIsPressed = Mouse.RightButton == MouseButtonState.Pressed;
                                return new IntPtr((int)MousePositionCodes.HTCAPTION);
                            case WindowMessages.WM_NCRBUTTONDOWN:
                                RButtonIsPressed = true;
                                return IntPtr.Zero;
                            default:
                                RButtonIsPressed = false;
                                return IntPtr.Zero;
                        }
                    });
        }

    }
}
